function Panorama (box, options) {
    if (typeof box == "string") box = document.querySelector(box);
    if (!box) return;
    var t = this, imageSize = options.imageSize, maxAxis = options.maxAxis, devicemotionEvent = false;
    t.elements = {
        box : box,
        model : document.createElement('div'),
        control : document.createElement('div')
    };
    if (options.boxSize) {
        if (typeof options.boxSize == "object") {
            Object.keys(options.boxSize).forEach(function (i) {
                if (typeof options.boxSize[i] == "number") options.boxSize[i] =  options.boxSize[i] + "px";
            });
            options.boxSize = {
                width : options.boxSize["width"] || options.boxSize[0],
                height : options.boxSize["height"] || options.boxSize[1]
            };
        } else {
            if (typeof options.boxSize == "number") options.boxSize = options.boxSize + "px";
            options.boxSize = { width : options.boxSize, height : options.boxSize };
        }
        t.elements.box.style.cssText += "width:" + options.boxSize["width"] + ";height:" + options.boxSize["height"];
    }
    if (!imageSize || imageSize == "auto") {
        var box = t.elements.box;
        if (box.offsetHeight && box.offsetWidth)
        imageSize = box.offsetHeight > box.offsetWidth ? box.offsetHeight : ~~(box.offsetWidth * 1.2);
        else
        imageSize = 1000;
    }
    if (maxAxis) {
        if (typeof maxAxis === "number") maxAxis = [maxAxis, maxAxis];
        if (Array.isArray(maxAxis)) maxAxis = [Math.abs(maxAxis[0]), Math.abs(maxAxis[1])];
    }
    if (options.devicemotionEvent && typeof options.devicemotionEvent != "string") devicemotionEvent = true;
    t.options = {
        images : options.images,
        imageSize : imageSize > 2000 ? 2000 : imageSize,
        boxSize : options.boxSize || "auto",
        perspective : options.perspective || 400,
        distance : options.distance || 400,
        originAxis : options.originAxis || options.axis || [0, 0],
        maxAxis : maxAxis || [90, 0],
        touchEvent : options.touchEvent === false ? false : true,
        devicemotionEvent : devicemotionEvent,
        motionElement : typeof options.motionElement === "string" ? document.querySelector(options.motionElement) : options.motionElement || t.elements.box,
        motionType : options.motionType || "easeOutStrong",
        motionTime : options.motionTime != void 0 ? options.motionTime : "auto",
        motionEnd : options.motionEnd ? function () { options.motionEnd(t) } : void 0
    };
    t.states = {
        motion : {
            animator : null,
            start : { pageX: 0, pageY: 0, cssX: 0, cssY: 0 },
            last : { pageX: 0, pageY: 0, cssX: 0, cssY: 0, disX: 0, disY: 0, time: 0, disTime: 0 }
        },
        touchmotion : {
            touches : [],
            touching : false,
            events : null
        },
        devicemotion : {
            originAxis : options.originAxis || options.axis || [0, 0],
            originFixed : false,
            event : null
        },
        hasEvents : {
            render : false,
            touchmotion : false,
            devicemotion : false,
        }
    };
    t.render();
    if (options.touchEvent !== false) t.touchmotion(true);
    if (devicemotionEvent) t.devicemotion(options.devicemotionEvent);
}

Panorama.prototype.methods = {
    css : function (config, set, get, setMethod, getMethod, getType) {
        var ele;
        if (config instanceof HTMLElement) {
            ele = config;
        } else if (typeof config === "object") {
            ele = config.element || config.ele,
            set = config.set,
            get = config.get,
            getType = config.getType,
            getMethod = config.getMethod,
            setMethod = config.setMethod;
        }
        if (typeof ele === "string") ele = document.querySelector(ele);
        if (!ele) return;

        function numGen (val) {
            var FEnum = String(val).match(/\-?(\d+\.|\.?)\d+(e|E)[\+\-]?\d+/),
                GEnum = String(val).match(/\-?(\d+\.|\.?)\d+/);
            if (FEnum) {
                var res = "", p1 = "", p2 = "", pm = 1; FEnum = FEnum[0];
                if (FEnum.indexOf("-") === 0) pm = 0, FEnum = FEnum.substr(1);
                FEnum = FEnum.replace(/^0+/, "").replace(/^\./, "0.");
                var dot = FEnum.indexOf("."),
                    power = Number(FEnum.match(/\d+$/)[0]) || 0,
                    ONnum = FEnum.replace(/(e|E).+$/, "").replace(/(\.\d+?)0+$/, "$1").replace(/\.0+$|\./g, "");
                if (dot != -1) {
                    p1 = ONnum.substring(0, dot),
                    p2 = ONnum.substring(dot, ONnum.length);
                }
                if (FEnum.indexOf("-") != -1) {
                    if (dot == -1) {
                        ONnum = ONnum.replace(/^0+/, "");
                        if (power < ONnum.length) {
                            res = ONnum.substr(0, ONnum.length - power) + "." + ONnum.substr(-power);
                        } else {
                            for (var i = 0, le = power - ONnum.length; i < le; i++) ONnum = "0" + ONnum;
                            res = "0." + ONnum;
                        }
                    } else {
                        if (!(/[^0]/.test(p1))) {
                            p1 = "0."; for (var i = 0; i < power; i++) p1 += "0";
                        } else if (power < p1.length) {
                            p1 = p1.substr(0, p1.length - power) + "." + p1.substr(-power);
                        } else {
                            for (var i = 0, le = power - p1.length; i < le; i++) p1 = "0" + p1;
                            p1 = "0." + p1;
                        }
                        res = p1 + p2;
                    }
                } else {
                    if (dot == -1) {
                        res = ONnum.replace(/^0+/, "");
                        for (var i = 0; i < power; i++) res += "0";
                    } else {
                        if (power < p2.length) {
                            p2 = p2.substr(0, power) + "." + p2.substr(power);
                        } else {
                            for (var i = 0, le = power - p2.length; i < le; i++) p2 += "0";
                        }
                        res = p1 + p2;
                    }
                }
                res = res.replace(/^0+(?!\.)/, "").replace(/(\.\d+?)0+$/, "$1").replace(/\.0+$|\.$/, "");
                if (!Number.isNaN(Number(res)) && !String(Number(res)).match(/e|E/) && Number.isFinite(Number(res))) {
                    res = pm < 1 ? -Number(res) : Number(res);
                } else {
                    res = pm < 1 ? "-" + res : res
                }
                return res;
            } else if (GEnum && !Number.isNaN(Number(GEnum[0])) && Number.isFinite(Number(GEnum[0]))) {
                return Number(GEnum[0]);
            } else {
                return typeof val != "number" && !Number.isNaN(Number(val)) && Number.isFinite(Number(val)) ? Number(val) : val;
            }
        }

        function u (val, unit, type) {
            var jo;
            if (type && Array.isArray(type)) jo = type[1], type = type[0];
            else if (!type) type = getType;
            if (type == "number") {
                var reNum = function (v) {
                    if (typeof v === "string" && /\d/.test(v)) {
                        var reVal = function (val, reps) {
                            if (reps != void 0) {
                                if (Array.isArray(reps)) {
                                    var tp = typeof val; val = String(val);
                                    reps.forEach(function (r, i) {
                                        if (r !== "" && r !== void 0) {
                                            r = new RegExp(r);
                                            if (r.test(val)) val = val.replace(r, "");
                                        }
                                    });
                                    if (tp == "number") val = Number(val);
                                } else if (reps !== "") {
                                    var tp = typeof val, r = new RegExp(reps);
                                    if (r.test(String(val))) {
                                        val = String(val).replace(r, "");
                                        val = tp == "number" ? Number(val) : val;
                                    }
                                }
                            }
                            return val;
                        };
                        var valSplit = function (vals, sp) {
                            var vARR = [];
                            reVal(vals, /url\(.*\)/g).split(sp).forEach(function (va) {
                                if (va.match(/\-?(\d+\.|\.?)\d+(e|E)[\+\-]?\d+/) || va.match(/\-?(\d+\.|\.?)\d+/)) {
                                    vARR.push(Number(numGen(va)));
                                } else {
                                    va = va.replace(/[^\d\.\-\+]/g, "");
                                    if (va && (Number(va) || Number(va) === 0))
                                    vARR.push(Number(va));
                                }
                            });
                            return vARR;
                        };
                        if (/.+,.+/.test(v)) return valSplit(v, ",");
                        else if (/[^,] .+/.test(v)) return valSplit(v, " ");
                        else return Number(numGen(reVal(v, /url\(.*\)/g)));
                    }
                    else return v;
                };
                if (Array.isArray(val)) {
                    var valARR = [];
                    val.forEach(function (v, i) { valARR[i] = reNum(v) });
                    return valARR;
                } else {
                    return reNum(val);
                }
            } else {
                if (Array.isArray(val)) {
                    var valARR = [];
                    val.forEach(function (v, i) {
                        var FEnum = String(v).match(/[\+\-]?(\d+\.|\.?)\d+(e|E)[\+\-]?\d+/);
                        if (FEnum) {
                            var ov = String(v).split(FEnum[0]);
                            val = ov[0] || "" + numGen(FEnum[0]) + ov[1] || "";
                        }
                        valARR[i] = unit ? v + unit : v;
                    });
                    return valARR.join(jo || ", ");
                } else {
                    var FEnum = String(val).match(/[\+\-]?(\d+\.|\.?)\d+(e|E)[\+\-]?\d+/);
                    if (FEnum) {
                        var ov = String(val).split(FEnum[0]);
                        val = ov[0] || "" + numGen(FEnum[0]) + ov[1] || "";
                    }
                    val = type == "string" ? String(val) : val;
                    return unit ? val + unit : val;
                }
            }
        }

        function getStyle (attr, valAttr, gMethod, vType) {
            var val; gMethod = gMethod || getMethod;
            if (gMethod == "inline") {
                val = ele.style[attr] != void 0 ? ele.style[attr] : "";
            } else if (gMethod == "computed") {
                val = ele.currentStyle ? ele.currentStyle[attr] : window.getComputedStyle(ele, null)[attr];
            } else if (ele.style[attr]) {
                val = ele.style[attr];
            } else {
                val = ele.currentStyle ? ele.currentStyle[attr] : window.getComputedStyle(ele, null)[attr];
            }
            if (valAttr && valAttr !== "value") {
                var valObj = {}, valObjAll = {}, oriValAttr = valAttr;
                if (typeof valAttr === "string") valAttr = [valAttr];
                if (/.+\(.*\)/.test(val)) {
                    val.split(") ").forEach(function (v, i, t) {
                        if (i === t.length - 1) v = v.replace(/\)$/, "");
                        var vARR = v.split("(");
                        valObjAll[vARR[0]] = u(vARR[1], null, vType);
                    });
                    if ((attr === "transform" || attr === "webkitTransform" || attr === "-webkit-transform") &&
                        valObjAll["matrix"] && oriValAttr !== "all" && Object.keys(valAttr).length < 2) {
                        var mv = getType == "number" ? valObjAll["matrix"] : u(valObjAll["matrix"], null, "number");
                        var deg = function (val, type) {
                            var d = function (v, t, m) {
                                if (t == "tan") m = Math.atan(v);
                                else if (t == "sin") m = Math.asin(v);
                                else m = Math.acos(v);
                                return m * 180 / Math.PI;
                            };
                            if (type == "skew") {
                                return d(val, "tan");
                            } else if (type == "rotate") {
                                var r1 = Math.round(Math.acos(val[0]) * 180 / Math.PI),
                                    r2 = Math.round(Math.asin(val[1]) * 180 / Math.PI),
                                    r3 = -Math.round(Math.asin(val[2]) * 180 / Math.PI),
                                    r4 = Math.round(Math.acos(val[3]) * 180 / Math.PI);
                                if (r1 == r2 && r1 == r3 && r1 == r4) return r1;
                            } else {
                                return d(val, type);
                            }
                        };
                        var matrixRotate = deg([mv[0],mv[1],mv[2],mv[3]], "rotate");
                        valObjAll = {
                            rotate : matrixRotate != void 0 ? u(matrixRotate, "deg") : "",
                            scaleX : mv[0],
                            scaleY : mv[3],
                            scale : u([mv[0], mv[3]]),
                            skewX : u(deg(mv[2], "skew"), "deg"),
                            skewY : u(deg(mv[1], "skew"), "deg"),
                            skew : u([deg(mv[2], "skew"), deg(mv[1], "skew")], "deg"),
                            translateX : u(mv[4], "px"),
                            translateY : u(mv[5], "px"),
                            translate : u([mv[4], mv[5]], "px"),
                            matrix : u(mv)
                        };
                    }
                }
                if (oriValAttr === "all") {
                    return valObjAll;
                } else {
                    Object.keys(valAttr).forEach(function (v) {
                        v = valAttr[v];
                        valObj[v] = valObjAll[v] != void 0 ? valObjAll[v] : "";
                    });
                    valObj.value = val;
                    return valObj;
                }
            } else {
                return u(val, null, vType);
            }
        }

        function getFN () {
            var valueObj = {};
            if (get == "all") {
                return ele.style;
            } else if (get == "cssText") {
                valueObj = { cssText : ele.style.cssText };
                for (var i = 0; i < ele.style.length; i++) {
                    valueObj[ele.style[i]] = ele.style[ele.style[i]];
                }
                return valueObj;
            } else if (Array.isArray(get)) {
                get.forEach(function (v) {
                    if (typeof v == "string") {
                        valueObj[v] = getStyle(v);
                    } else {
                        Object.keys(v).forEach(function (va) {
                            valueObj[va] = getStyle(va, v[va]);
                        });
                    }
                });
                return valueObj;
            } else if (typeof get == "object") {
                Object.keys(get).forEach(function (v) {
                    valueObj[v] = getStyle(v, get[v]);
                });
                return valueObj;
            } else if (typeof get == "string") {
                return getStyle(get);
            }
        }

        function setFN () {
            if (typeof set === "string") {
                ele.style.cssText += set;
            } else if (setMethod == "add") {
                Object.keys(set).forEach(function (a) {
                    if (typeof set[a] === "string") {
                        ele.style[a] = set[a];
                    } else {
                        var oriVal = getStyle(a, null, "auto", "string");
                        if (/\d/.test(oriVal)) {
                            if (typeof set[a] === "number") {
                                var nu = Number(oriVal.replace(/[^\d\.\-\+]/g, "")) || "",
                                    un = oriVal.match(/[^\d\.\-\+]+$/),
                                    FEnum = oriVal.match(/\-?(\d+\.|\.?)\d+(e|E)[\+\-]?\d+/);
                                if (FEnum && !Number.isNaN(Number(FEnum[0]))) nu = Number(FEnum[0]);
                                ele.style[a] = numGen(set[a] + nu) + (un ? un[0] : "");
                            } else if (typeof set[a] === "object") {
                                var reSet = function (oriGup, setGup) {
                                    var reGup = function (v) {
                                        var valSplit = function (vals, sp) {
                                            var vARR = [sp];
                                            vals.replace(/url\(.*\)/g, "").split(sp).forEach(function (v) {
                                                v = v.replace(/^ +/, "").replace(/ +$/, "");
                                                var nu = v.replace(/[^\d\.\-\+]/g, ""),
                                                    un = v.match(/[^\d\.\-\+]+$/),
                                                    FEnum = v.match(/\-?(\d+\.|\.?)\d+(e|E)[\+\-]?\d+/);
                                                if (FEnum && !Number.isNaN(Number(FEnum[0]))) nu = FEnum[0];
                                                if (nu && (Number(nu) || Number(nu) === 0))
                                                vARR.push([Number(nu), un ? un[0] : ""]);
                                            });
                                            return vARR;
                                        };
                                        if (/.+,.+/.test(v)) v = valSplit(v, ", ");
                                        else if (/[^,] .+/.test(v)) v = valSplit(v, " ");
                                        return v;
                                    };
                                    var setVal = "", oriGup = reGup(oriGup);
                                    var sp = oriGup[0] || " ", un = "", le = setGup.length;
                                    if (Array.isArray(setGup[le - 1])) {
                                        le = le - 1;
                                        if (setGup[le][0] || setGup[le][0] === "") {
                                            sp = setGup[le][0];
                                        }
                                        if (setGup[le][1]) {
                                            un = setGup[le][1];
                                        }
                                    }
                                    for (var i = 0; i < le; i++) {
                                        if (typeof setGup[i] === "number") {
                                            var oG = oriGup[i+1] ? oriGup[i+1] : ["", ""];
                                            setVal += numGen(setGup[i] + oG[0]) + (un ? un : oG[1]) + sp;
                                        } else {
                                            setVal += setGup[i] + un + sp;
                                        }
                                    }
                                    return sp === "" ? setVal : setVal.replace(RegExp(sp + "+$"), "").replace(/,* *$/, "");
                                };
                                if (Array.isArray(set[a])) {
                                    ele.style[a] = reSet(oriVal, set[a]);
                                } else {
                                    var setVal = "", valARR = getStyle(a, "all", "auto", "string");
                                    Object.keys(set[a]).forEach(function (v) {
                                        if (valARR[v]) {
                                            if (typeof set[a][v] === "string") {
                                                valARR[v] = set[a][v];
                                            } else if (/\d/.test(valARR[v])) {
                                                if (typeof set[a][v] === "number") {
                                                    var nu = Number(valARR[v].replace(/[^\d\.\-\+]/g, "")) || "",
                                                        un = valARR[v].match(/[^\d\.\-\+]+$/),
                                                        FEnum = valARR[v].match(/\-?(\d+\.|\.?)\d+(e|E)[\+\-]?\d+/);
                                                    if (FEnum && !Number.isNaN(Number(FEnum[0]))) nu = Number(FEnum[0]);
                                                    valARR[v] = numGen(set[a][v] + nu) + (un ? un[0] : "");
                                                } else if (Array.isArray(set[a][v])) {
                                                    valARR[v] = reSet(valARR[v], set[a][v]);
                                                }
                                            }
                                        }
                                    });
                                    Object.keys(valARR).forEach(function (v) {
                                        if (v !== "value") setVal += v + "(" + valARR[v] + ") ";
                                    });
                                    ele.style[a] = setVal.replace(/,* *$/, "");
                                }
                            }
                        }
                    }
                });
            } else {
                var reSet = function (val) {
                    if (Array.isArray(val)) {
                        var setVal = "", sp = " ", un = "", le = val.length;
                        if (Array.isArray(val[le - 1])) {
                            le = le - 1;
                            if (val[le][0] || val[le][0] === "") {
                                sp = val[le][0];
                            }
                            if (val[le][1]) {
                                un = val[le][1];
                            }
                        }
                        for (var i = 0; i < le; i++) {
                            setVal += val[i] + un + sp;
                        }
                        return sp === "" ? setVal : setVal.replace(RegExp(sp + "+$"), "");
                    } else {
                        return val;
                    }
                };
                Object.keys(set).forEach(function (a) {
                    if (typeof set[a] === "object") {
                        if (Array.isArray(set[a])) {
                            ele.style[a] = reSet(set[a]);
                        } else {
                            var setVal = "", valARR = getStyle(a, "all", "auto", "string");
                            Object.keys(set[a]).forEach(function (v) {
                                valARR[v] = reSet(set[a][v]);
                            });
                            Object.keys(valARR).forEach(function (v) {
                                if (v !== "value") setVal += v + "(" + valARR[v] + ") ";
                            });
                            ele.style[a] = setVal.replace(/,* *$/, "");
                        }
                    } else {
                        ele.style[a] = set[a];
                    }
                });
            }
        }

        if (set && get) {
            var fn1;
            Object.keys(config).forEach(function (v) {
                if (fn1) return;
                else if (v === "set" || v === "get") fn1 = v;
            });
            if (fn1 === "set") {
                setFN(); return getFN();
            } else {
                var getRes = getFN(); setFN(); return getRes;
            }
        } else if (set) {
            setFN();
        } else if (get) {
            return getFN();
        }
    },

    animate : function (element, config) {
        /**
         * CSS动画函数
         * @param {String|HTMLElement} element - 全景图DOM元素
         * @param {Object} config - 配置
         * @property {Number} config.style - 动画样式
         * @property {Number} [config.time = 1000] - 在指定时间（ms）内完成动画，默认1000ms
         * @property {String} [config.type = 'easeOutStrong'] - 动画类型，默认easeOutStrong
         * @property {Array} config.targets - 目标值数组，类似[[0, 100], [100, 0]]：值从0到100或从100到0为止，变化的数值会作为options.running函数的参数
         * @property {Function} config.running - 动画执行中的回调
         * @property {Function} config.end - 结束后的回调
        **/
        
        if (!(element instanceof HTMLElement) && typeof element === "object") {
            config = element;
            element = config.element;
        }
        var css = this.css,
            timer = null,
            style = config.style,
            time = config.time || 1000,
            type = config.type || "easeOutStrong",
            targets = config.targets || [],
            startTime = (new Date().getTime());
        var requestAnimationFrame = (function () {
                return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame ||
                    window.msRequestAnimationFrame || function (callback) { return window.setTimeout(callback, 1000 / 60); }
            })(),
            cancelAnimationFrame = (function () {
                return window.cancelAnimationFrame || window.mozCancelAnimationFrame || function (id) { clearTimeout(id) }
            })();
        var step = function () {
            var value = [], setcss = { transform : {} }, changeTime = (new Date().getTime());
            var scale = 1 - ((Math.max(0, startTime - changeTime + time) / time) || 0);
            var Tween = {
                // t : 当前时间   b : 初始值   c : 变化值   d : 总时间   return : 当前的位置
                linear: function (t, b, c, d) {
                    return c * t / d + b;
                },
                easeOutStrong: function (t, b, c, d) {
                    return -c * ((t = t / d - 1) * t * t * t - 1) + b;
                },
                easeInQuad: function (t, b, c, d) {
                    return c * (t /= d) * t + b;
                },
                easeOutQuad: function (t, b, c, d) {
                    return -c * (t /= d) * (t - 2) + b;
                },
                easeInOutQuad: function (t, b, c, d) {
                    if ((t /= d / 2) < 1) {
                        return c / 2 * t * t + b;
                    } else {
                        return -c / 2 * ((--t) * (t - 2) - 1) + b;
                    }
                },
                easeInCubic: function (t, b, c, d) {
                    return c * (t /= d) * t * t + b;
                },
                easeOutCubic: function (t, b, c, d) {
                    return c * ((t = t / d - 1) * t * t + 1) + b;
                },
                easeInOutCubic: function (t, b, c, d) {
                    if ((t /= d / 2) < 1) {
                        return c / 2 * t * t * t + b;
                    } else {
                        return c / 2 * ((t -= 2) * t * t + 2) + b;
                    }
                },
                easeInQuart: function (t, b, c, d) {
                    return c * (t /= d) * t * t * t + b;
                },
                easeOutQuart: function (t, b, c, d) {
                    return -c * ((t = t / d - 1) * t * t * t - 1) + b;
                },
                easeInOutQuart: function (t, b, c, d) {
                    if ((t /= d / 2) < 1) {
                        return c / 2 * t * t * t * t + b;
                    } else {
                        return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
                    }
                },
                easeInQuint: function (t, b, c, d) {
                    return c * (t /= d) * t * t * t * t + b;
                },
                easeOutQuint: function (t, b, c, d) {
                    return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
                },
                easeInOutQuint: function (t, b, c, d) {
                    if ((t /= d / 2) < 1) {
                        return c / 2 * t * t * t * t * t + b;
                    } else {
                        return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
                    }
                },
                easeInSine: function (t, b, c, d) {
                    return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
                },
                easeOutSine: function (t, b, c, d) {
                    return c * Math.sin(t / d * (Math.PI / 2)) + b;
                },
                easeInOutSine: function (t, b, c, d) {
                    return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
                },
                easeInExpo: function (t, b, c, d) {
                    return c * Math.pow(2, 10 * (t / d - 1)) + b;
                },
                easeOutExpo: function (t, b, c, d) {
                    return c * (-Math.pow(2, -10 * t / d) + 1) + b;
                },
                easeInOutExpo: function (t, b, c, d) {
                    if (t === 0) {
                    }
                    if (t === d) {
                    // b + c
                    }
                    if ((t /= d / 2) < 1) {
                        return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
                    } else {
                        return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
                    }
                },
                easeInCirc: function (t, b, c, d) {
                    return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
                },
                easeOutCirc: function (t, b, c, d) {
                    return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
                },
                easeInOutCirc: function (t, b, c, d) {
                    if ((t /= d / 2) < 1) {
                        return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
                    } else {
                        return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
                    }
                },
                easeInElastic: function (t, b, c, d) {
                    var a = c, p = 0, s = 1.70158;
                    if (t === 0) {
                        // b
                    } else if ((t /= d) === 1) {
                        // b + c
                    }
                    if (!p) {
                        p = d * 0.3;
                    }
                    if (a < Math.abs(c)) {
                        a = c, s = p / 4;
                    } else {
                        s = p / (2 * Math.PI) * Math.asin(c / a);
                    }
                    return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
                },
                easeOutElastic: function (t, b, c, d) {
                    var a = c, p = 0, s = 1.70158;
                    if (t === 0) {
                        // b
                    } else if ((t /= d) === 1) {
                        // b + c
                    }
                    if (!p) {
                        p = d * 0.3;
                    }
                    if (a < Math.abs(c)) {
                        a = c, s = p / 4;
                    } else {
                        s = p / (2 * Math.PI) * Math.asin(c / a);
                    }
                    return a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b;
                },
                easeInOutElastic: function (t, b, c, d) {
                    var a = c, p = 0, s = 1.70158;
                    if (t === 0) {
                        // b
                    } else if ((t /= d / 2) === 2) {
                        // b + c
                    }
                    if (!p) {
                        p = d * (0.3 * 1.5);
                    }
                    if (a < Math.abs(c)) {
                        a = c, s = p / 4;
                    } else {
                        s = p / (2 * Math.PI) * Math.asin(c / a);
                    }
                    if (t < 1) {
                        return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
                    } else {
                        return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * 0.5 + c + b;
                    }
                },
                easeInBack: function (t, b, c, d, s) {
                    if (s === void 0) {
                        s = 1.70158;
                    }
                    return c * (t /= d) * t * ((s + 1) * t - s) + b;
                },
                easeOutBack: function (t, b, c, d, s) {
                    if (s === void 0) {
                        s = 1.70158;
                    }
                    return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
                },
                easeInOutBack: function (t, b, c, d, s) {
                    if (s === void 0) {
                        s = 1.70158;
                    }
                    if ((t /= d / 2) < 1) {
                        return c / 2 * (t * t * (((s *= 1.525) + 1) * t - s)) + b;
                    } else {
                        return c / 2 * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2) + b;
                    }
                },
                easeInBounce: function (t, b, c, d) {
                    var v = Tween.easeOutBounce(d - t, 0, c, d);
                    return c - v + b;
                },
                easeOutBounce: function (t, b, c, d) {
                    if ((t /= d) < 1 / 2.75) {
                        return c * (7.5625 * t * t) + b;
                    } else if (t < 2 / 2.75) {
                        return c * (7.5625 * (t -= 1.5 / 2.75) * t + 0.75) + b;
                    } else if (t < 2.5 / 2.75) {
                        return c * (7.5625 * (t -= 2.25 / 2.75) * t + 0.9375) + b;
                    } else {
                        return c * (7.5625 * (t -= 2.625 / 2.75) * t + 0.984375) + b;
                    }
                },
                easeInOutBounce: function (t, b, c, d) {
                    var v;
                    if (t < d / 2) {
                        return Tween.easeInBounce(t * 2, 0, c, d) * 0.5 + b;
                    } else {
                        return Tween.easeOutBounce(t * 2 - d, 0, c, d) * 0.5 + c * 0.5 + b;
                    }
                }
            }
            for (var prop in style) {
                targets.push([css(element, null, { transform : prop }, null, null, "number").transform[prop], style[prop]]);
            }
            targets.forEach(function (target) {
                value.push(target[0] === target[1] ? target[0] : (function () {
                    try { return Tween[type](scale * time, target[0], target[1] - target[0], time) }
                    catch (e) {
                        try { return Tween.easeOutStrong(scale * time, target[0], target[1] - target[0], time) }
                        catch (e) { return target[0] || 0 }
                    }
                })());
            });
            Object.keys(style).forEach(function (a, i) {
                var un = "", v = value[i];
                if (a.indexOf("rotate") != -1 || a.indexOf("skew") != -1) un = "deg";
                else if (a.indexOf("translate") != -1) un = "px";
                setcss.transform[a] = v + un;
            });
            css(element, setcss);
            if (scale === 1) {
                cancelAnimationFrame(timer);
                config.end && config.end();
            } else {
                timer = requestAnimationFrame(step);
            }
        };
        cancelAnimationFrame(timer);
        timer = requestAnimationFrame(step);
        return {
            style: style,
            time: time,
            type: type,
            targets: targets,
            stop: function () { cancelAnimationFrame(timer) }
        }
    }
}

Panorama.prototype.render = function () {
    var el = this.elements, op = this.options;
    el.box.style.cssText += "overflow:hidden;-webkit-user-select:none;-moz-user-select:none;user-select:none;"+
        "-webkit-perspective:" + op.perspective + "px;perspective:" + op.perspective + "px";
    el.model.style.cssText = "position:absolute;left:0;top:0;bottom:0;right:0;"+
        "-webkit-transform-style:preserve-3d;transform-style:preserve-3d;"+
        "-webkit-transform:translateZ(" + op.distance + "px);transform:translateZ(" + op.distance + "px);";
    el.control.style.cssText = "position:absolute;top:50%;left:50%;-webkit-transform-style:preserve-3d;transform-style:preserve-3d;"+
        "width:" + op.imageSize + "px;height:" + op.imageSize + "px;margin:-" + (op.imageSize / 2) + "px 0 0 -" + (op.imageSize / 2) + "px;";
    el.control.style.WebkitTransform = el.control.style.transform = "rotateX(" + op.originAxis[0] + "deg) rotateY(" + op.originAxis[1] + "deg)";
    el.control.innerHTML = (function () {
        var html = [], getRotate = function (i) {
            return i < 4 ? "rotateY(-" + (i * 90) + "deg)" : "rotateX(" + (i === 4 ? -90 : 90) + "deg)";
        };
        for (var i = 0; i < op.images.length; i++) {
            html.push("<div style='position:absolute;width:100%;height:100%;top:0;left:0;background-size:100% 100%;"+
            "background-repeat:no-repeat;-webkit-backface-visibility:hidden;backface-visibility:hidden;"+
            "-webkit-transfrom:" + getRotate(i) + " translateZ(-" + (op.imageSize / 2 - 2.5) + "px);"+
            "transform:" + getRotate(i) + " translateZ(-" + (op.imageSize / 2 - 2.5) + "px);"+
            "background-image:url(" + op.images[i] + ");'></div>");
        }
        return html.join('');
    })();
    el.model.appendChild(el.control);
    el.box.appendChild(el.model);
    this.states.hasEvents.render = true;
}

Panorama.prototype.motion = function (config) {
    var t = this;
    if (typeof config === "number" || Array.isArray(config)) config = { axis : config };
    else if (!config) config = {};
    var el = t.elements,
        ms = t.states.motion,
        css = t.methods.css;
    var start = ms.start,
        last = ms.last,
        axis = typeof config.axis === "number" ? [config.axis, config.axis] : config.axis || [0, 0],
        axisType = config.axisType,
        motionType = config.motionType || config.type || t.options.motionType,
        end = config.end ? function () { config.end(t) } : void 0,
        time = config.time != void 0 ? config.time : 1000,
        nowTime = ~~new Date();
    ms.animator && ms.animator.stop();
    var startCSS = css(el.control, null, { transform : ["rotateX", "rotateY"] }, null, null, "number").transform;
    start.cssX = startCSS.rotateY;
    start.cssY = startCSS.rotateX;
    var getRotate = function (val, dir) {
        if (axisType === "add" || axisType === 1) {
            return val + (dir === 2 ? start.cssX : start.cssY);
        } else if ((axisType === "rule" || axisType === 2) && (val < -180 || val > 180)) {
            return val > 0 ? val % 180 - 180 : 180 + val % 180;
        } else {
            return val;
        }
    };
    var rotateX = getRotate(axis[1], 1),
        rotateY = getRotate(axis[0], 2),
        translateZ;
        // translateZ = axis[2];
    if (translateZ) {
        start.cssZ = css(el.model, null, { transform : "translateZ" }, null, null, "number").transform.translateZ;
        if (axisType === "add" || axisType === 1) translateZ = axis[2] + start.cssZ;
        if (rotateX === start.cssX && rotateY === start.cssY && translateZ === start.cssZ) return;
        last.cssZ = translateZ;
        last.disZ = translateZ - start.cssZ;
    } else if (rotateX === start.cssX && rotateY === start.cssY) {
        return;
    }
    var disX = rotateX - start.cssX,
        disY = rotateY - start.cssY;
    var speedX = time > 0 ? disX / time : 0,
        speedY = time > 0 ? disY / time : 0;
    if (time !== 0) {
        var style = { rotateX : rotateX, rotateY : rotateY };
        if (translateZ) style[translateZ] = translateZ;
        ms.animator = t.methods.animate(el.control, {
            style: style,
            type: motionType,
            time: time,
            end: end,
        });
        ms.animator.speed = speedX > speedY ? speedX : speedY;
    } else {
        css(el.control, { transform : { rotateX : rotateX + "deg", rotateY : rotateY + "deg" } });
        if (translateZ) css(el.model, { transform : { translateZ : translateZ + "px" } });
        end && end();
    }
    start.pageX = 0;
    start.pageY = 0;
    last.pageX = 0;
    last.pageY = 0;
    last.cssX = rotateX;
    last.cssY = rotateY;
    last.disX = disX;
    last.disY = disY;
    last.disTime = time;
    start.time = nowTime;
    last.time = nowTime + time;
}

Panorama.prototype.touchmotion = function (mand) {
    var t = this;
    var el = t.elements,
        op = t.options,
        ms = t.states.motion,
        ts = t.states.touchmotion,
        css = t.methods.css;
    if (mand === "remove" || mand === null) {
        if (ts.events != null) {
            op.motionElement.removeEventListener('touchstart', ts.events.touchstart);
            op.motionElement.removeEventListener('touchmove', ts.events.touchmove);
            op.motionElement.removeEventListener('touchend', ts.events.touchend);
            op.motionElement.removeEventListener('mousedown', ts.events.touchstart);
            op.motionElement.removeEventListener('mouseup', ts.events.touchend);
            op.motionElement.removeEventListener('mouseleave', ts.events.mouseleave);
            t.states.hasEvents.touchmotion = false; op.touchEvent = false;
            ts.events = null; ts.touching = false; ts.touches = [];
        }
        return;
    } else if (mand === true) {
        op.touchEvent = true; if (ts.events != null) return;
    } else if (config === false) {
        op.touchEvent = false; return;
    } else if (config === void 0 || config === "switch") {
        op.touchEvent = op.touchEvent ? false : true;
        if (ts.events != null) return;
    }
    t.states.hasEvents.touchmotion = true, op.touchEvent = true;
    if (ts.events != null) window.removeEventListener("devicemotion", ds.event, true);
    ts.events = {
        touchstart : function (e) {
            ts.touching = true;
            if (t.options.touchEvent === false) return;
            e.preventDefault();
            var start = ms.start, last = ms.last;
            var touches = e.changedTouches || e.touches;
            if (touches) {
                ts.touches.unshift({
                    prev: { pageX: touches[0].pageX, pageY: touches[0].pageY },
                    last: { pageX: touches[0].pageX, pageY: touches[0].pageY }
                });
                if (ts.touches.length > 1) return;
            }
            ms.animator && ms.animator.stop();
            var startCSS = css(el.control, null, { transform : ["rotateX", "rotateY"] }, null, null, "number").transform,
                pageX = e.pageX != void 0 ? e.pageX : touches[0].pageX || 0,
                pageY = e.pageY != void 0 ? e.pageY : touches[0].pageY || 0;
            start.pageX = pageX;
            start.pageY = pageY;
            start.cssY = startCSS.rotateX;
            start.cssX = startCSS.rotateY;
            last.time = ~~new Date();
            last.disX = 0;
            last.disY = 0;
            last.cssX = start.cssX;
            last.cssY = start.cssY;
            last.disTime = 0;
            this.addEventListener('mousemove', ts.events.touchmove);
            this.addEventListener('mouseleave', ts.events.mouseleave);
        },
        touchmove : function (e) {
            if (!ts.touching) ts.touching = true;
            if (t.options.touchEvent === false) return;
            var start = ms.start, last = ms.last;
            var touches = e.changedTouches || e.touches;
            if (touches) {
                for (var i = 0; i < touches.length; i++) {
                    try {
                        ts.touches[i].prev = { pageX: ts.touches[i].last.pageX, pageY: ts.touches[i].last.pageY };
                        ts.touches[i].last = { pageX: touches[i].pageX, pageY: touches[i].pageY };
                    } catch (er) {}
                }
                if (ts.touches.length > 1) return;
            }
            var pageX = e.pageX != void 0 ? e.pageX : touches[0].pageX || 0,
                pageY = e.pageY != void 0 ? e.pageY : touches[0].pageY || 0;
            var disX = (pageX - start.pageX) / 10,
                disY = (pageY - start.pageY) / 10;
            var cssX = start.cssX - disX,
                cssY = start.cssY + disY,
                nowTime = ~~new Date();
            css(el.control, { transform : { rotateX : cssY + "deg", rotateY : cssX + "deg" } });
            last.pageX = pageX;
            last.pageY = pageY;
            last.disX = cssX - last.cssX;
            last.disY = cssY - last.cssY;
            last.cssX = cssX;
            last.cssY = cssY;
            last.disTime = nowTime - last.time;
            last.time = nowTime;
        },
        touchend : function () {
            ts.touching = false;
            if (t.options.touchEvent === false) return;
            if (ts.touches.length > 1) { ts.touches.shift(); return }
            else ts.touches = [];
            this.removeEventListener('mousemove', ts.events.touchmove);
            this.removeEventListener('mouseleave', ts.events.mouseleave);
            var start = ms.start, last = ms.last, max = op.maxAxis, remotion, motionEnd;
            if (last.cssX === start.cssX && last.cssY === start.cssY) return;
            var speedY = last.disX / last.disTime,
                speedX = last.disY / last.disTime;
            var disX = parseInt(speedX * 200),
                disY = parseInt(speedY * 200);
            var speed = (Math.abs(disX) > Math.abs(disY) ? speedX : speedY) || 0;
            var time = Number(op.motionTime) || Math.abs(parseInt(speed * 200)) * 15;
            var rotateX = last.cssY + disX,
                rotateY = last.cssX + disY;
            if (max) {
                if (max[0] > 0 && (rotateX > max[0] || rotateX < -max[0])) {
                    remotion = true;
                    rotateX = rotateX > max[0] ? max[0] : -max[0];
                }
                if (max[1] > 0 && (rotateY > max[1] || rotateY < -max[1])) {
                    remotion = true;
                    rotateY = rotateY > max[1] ? max[1] : -max[1];
                }
                if (remotion && Math.abs(speed) <= 0.1 && op.motionTime !== 0) {
                    time = Number(op.motionTime) || 800;
                }
            }
            if (t.states.hasEvents.devicemotion && t.states.devicemotion.originFixed !== true) {
                motionEnd = function () {
                    var nowCSS = css(el.control, null, { transform : ["rotateX", "rotateY"] }, null, null, "number").transform;
                    t.states.devicemotion.originAxis = [nowCSS.rotateX || 0, nowCSS.rotateY || 0];
                    op.motionEnd && op.motionEnd();
                }
            } else {
                motionEnd = op.motionEnd;
            }
            if ((Math.abs(speed) > 0.1 && op.motionTime !== 0) || remotion) {
                ms.animator = t.methods.animate(el.control, {
                    style: { rotateX : rotateX, rotateY : rotateY },
                    type: op.motionType,
                    time: time,
                    end: motionEnd
                });
                ms.animator.speed = speed;
            } else if (motionEnd) {
                motionEnd();
            }
        },
        mouseleave : function () {
            if (t.options.touchEvent === false) return;
            this.removeEventListener('mousemove', ts.events.touchmove);
            this.removeEventListener('mouseleave', ts.events.mouseleave);
        }
    };

    op.motionElement.addEventListener('touchstart', ts.events.touchstart);
    op.motionElement.addEventListener('touchmove', ts.events.touchmove);
    op.motionElement.addEventListener('touchend', ts.events.touchend);
    op.motionElement.addEventListener('mousedown', ts.events.touchstart);
    op.motionElement.addEventListener('mouseup', ts.events.touchend);
    op.motionElement.addEventListener('mouseleave', ts.events.mouseleave);
}

Panorama.prototype.devicemotion = function (config) {
    var t = this, op = t.options, ds = t.states.devicemotion;
    if (config === "remove" || config === null) {
        if (ds.event != null) {
            window.removeEventListener("devicemotion", ds.event, true);
            t.states.hasEvents.devicemotion = false; op.devicemotionEvent = false;
            ds.event = null; delete ds.motion; delete ds.speed; delete ds.rate; delete ds.range;
        }
        return;
    } else if (config === true) {
        op.devicemotionEvent = true; if (ds.event != null) return;
    } else if (config === false) {
        op.devicemotionEvent = false; return;
    } else if (config === void 0 || config === "switch") {
        op.devicemotionEvent = op.devicemotionEvent ? false : true;
        if (ds.event != null) return;
    }
    t.states.hasEvents.devicemotion = true, op.devicemotionEvent = true;
    if (ds.event != null) window.removeEventListener("devicemotion", ds.event, true);
    if (!config || typeof config !== "object") config = {};
    ds.speed = config.speed || 2,
    ds.rate = config.rate || 6,
    ds.range = config.range || 60;
    if (!config.originAxis) {
        var nowCSS = t.methods.css(t.elements.control, null, { transform : ["rotateX", "rotateY"] }, null, null, "number").transform;
        ds.originAxis = [nowCSS.rotateX || 0, nowCSS.rotateY || 0];
    }
    else ds.originAxis = config.originAxis;
    if (config.originFixed != void 0) ds.originFixed = config.originFixed;
    ds.event = function (e) {
        if (t.states.touchmotion.touching || ds.enable === false) return;
        var time = config.time != void 0 ? config.time : 500,
            // nowTime = ~~new Date(),
            dm = ds.motion,
            oa = ds.originAxis,
            ac = e.accelerationIncludingGravity;
        if (dm) {
            var axis = [oa[0], oa[1]],
                cx = Math.round((ac.x - dm.lastX) * ds.rate),
                cz = Math.round((ac.z - dm.lastZ) * ds.rate),
                nowCSS = t.methods.css(t.elements.control, null, { transform : ["rotateX", "rotateY"] }, null, null, "number").transform;
            var rx = cx + nowCSS.rotateX, rz = cz + nowCSS.rotateY;
            axis[0] = rx >= oa[0] - ds.range && rx <= oa[0] + ds.range ? rx : (rx < oa[0] - ds.range ? oa[0] - ds.range : oa[0] + ds.range);
            axis[1] = rz >= oa[1] - ds.range && rz <= oa[1] + ds.range ? rz : (rz < oa[1] - ds.range ? oa[1] - ds.range : oa[1] + ds.range);
            // if (config.time == void 0 || config.time == "auto") {
            //     time = Math.abs(parseInt((cx > cz ? cx : cz) / (nowTime - dm.lastTime) * 200)) * 15;
            // }
	        if (Math.abs(cx) > ds.speed || Math.abs(cz) > ds.speed) {
                t.motion({
                    axis: axis,
                    time: time,
                    type: "easeOutCubic",
                    end: config.end
                });
	        }
        }
        ds.motion = { lastX : ac.x, lastZ : ac.z };
        // window.devicemotion.lastTime = nowTime;
    }
    window.addEventListener("devicemotion", ds.event, true);
}

Panorama.prototype.delete = function () {
    if (this.states.hasEvents.render) return;
    else this.states.hasEvents.render = false;
    var el = this.elements;
    el.box.style.webkitPerspective = el.box.style.perspective = "";
    el.box.removeChild(el.model);
}

Panorama.prototype.recovery = function () {
    if (!this.states.hasEvents.render) return;
    else this.states.hasEvents.render = true;
    var el = this.elements;
    el.box.style.webkitPerspective = el.box.style.perspective = this.options.perspective + "px";
    el.box.appendChild(el.model);
}

Panorama.prototype.reset = function (options) {
    var t = this, imageSize = options.imageSize;
    var op = t.options, el = t.elements, css = t.methods.css;
    if (options.boxSize) {
        if (typeof options.boxSize == "object") {
            Object.keys(options.boxSize).forEach(function (i) {
                if (typeof options.boxSize[i] == "number") options.boxSize[i] =  options.boxSize[i] + "px";
            });
            options.boxSize = {
                width : options.boxSize["width"] || options.boxSize[0],
                height : options.boxSize["height"] || options.boxSize[1]
            };
        } else {
            if (typeof options.boxSize == "number") options.boxSize = options.boxSize + "px";
            options.boxSize = { width : options.boxSize, height : options.boxSize };
        }
        op.boxSize = options.boxSize;
        el.box.style.cssText += "width:" + options.boxSize["width"] + ";height:" + options.boxSize["height"];
    }
    if (imageSize == "auto") {
        if (el.box.offsetHeight && el.box.offsetWidth)
        imageSize = el.box.offsetHeight > el.box.offsetWidth ? el.box.offsetHeight : ~~(el.box.offsetWidth * 1.2);
        else
        imageSize = 1000;
    } else if (imageSize > 2000) {
        imageSize = 2000;
    }
    if (imageSize) {
        op.imageSize = imageSize;
        el.control.style.cssText += "width:" + imageSize + "px;height:" + imageSize + "px;margin:-" + (imageSize / 2) + "px 0 0 -" + (imageSize / 2) + "px;";
        for (var i = 0; i < el.control.children.length; i++) {
            css(el.control.children[i], {
                webkitTransform : { translateZ : -(imageSize / 2 - 2.5) + "px" },
                transform : { translateZ : -(imageSize / 2 - 2.5) + "px" }
            });
        }
    }
    if (options.images) {
        op.images = options.images;
        for (var i = 0; i < el.control.children.length; i++) {
            if (op.images[i]) el.control.children[i].style.backgroundImage = "url(" + op.images[i] + ")";
        }
    }
    if (options.perspective) {
        op.perspective = options.perspective;
        el.box.style.cssText += "-webkit-perspective:" + op.perspective + "px;perspective:" + op.perspective + "px";
    }
    if (options.distance) {
        op.distance = options.distance;
        el.model.style.cssText += "-webkit-transform:translateZ(" + op.distance + "px);transform:translateZ(" + op.distance + "px);";
    }
    if (options.devicemotionEvent != void 0) t.devicemotion(options.devicemotionEvent);
    if (options.touchEvent != void 0) t.touchmotion(options.touchEvent);
    if (options.motionTime != void 0) op.motionTime = options.motionTime;
    if (options.motionType) op.motionType = options.motionType;
    if (options.motionEnd) op.motionEnd = function () { options.motionEnd(t) };
    if (options.motionElement) op.motionElement = typeof options.motionElement === "string" ?
        document.querySelector(options.motionElement) : options.motionElement || op.motionElement;
}